function TextStyler(textModel){
    this.initTokenizer();
    this.init$Eventable();
    this.$textModel = textModel;
    this.updateTokens(0, 0, textModel.getText());
    this.on(this.$textModel, 'Changing', this._onTextChanging);
    this.$mode = new JavascriptMode();
//    this.limitedUpdate = Utils.thinFunction(25, Utils.bind(this.update, this));
}

function Token(type, length){
    this.type = type;
    this.length = length;
}
Token.prototype.slice = function (startOffset, endOffset){
    var r = Utils.calcSliceArguments(startOffset, endOffset, this.length);
    return new Token(this.type, r[1] - r[0]);
};

TextStyler.prototype = {
    getTokens: function (startOffset, endOffset){
        var tokens = [];
        this.walkTokens(startOffset, endOffset, function (token){
            tokens.push(token);
        });
        return tokens;
    },
    _onTextChanging: function (evt){
        var offset = evt.offset;
        this.updateTokens(offset, offset + evt.deletedText.length, evt.insertedText);
        this.$dirtyOffset = Math.max(offset - 2, 0);
        this.$dirtyEndOffset = evt.offset + evt.insertedText.length + 1;
        clearTimeout(this._updateTimer);
        var this_ = this;
        this._updateTimer = setTimeout(function (){
            this_.update();
        });
    },
    update: function (){
        var dirtyOffset = this.$dirtyOffset;
        var dirtyEndOffset = this.$dirtyEndOffset;
        var r1 = this.findNode(dirtyOffset);
        var node = r1.node;
        var nodeOffset = r1.nodeOffset;
        var startOffset = r1.nodeOffset;
        var mode = this.$mode;
        var text = this.$textModel.getText(startOffset);
        var stream = new Stream(text);
        var state = mode.startState();

        var tokens = [];
        var endOffset = text.length;
        var tokenOffset = 0;
        var lastOffset;
        var succ = false;
        var over = false;
        while (!stream.isEnd()) {
            var type = mode.token(stream, state);
            tokenOffset = stream.index0;
            lastOffset = stream.index;
            tokens.push(new Token(type, lastOffset - tokenOffset));
            while (node != null) {
                if (nodeOffset >= startOffset + lastOffset) {
                    break;
                }
                nodeOffset += node.length;
                node = node.next;
            }
            if (nodeOffset === startOffset + lastOffset) {
                succ = true;
                if (over) {
                    break;
                }
                if (startOffset + lastOffset >= dirtyEndOffset) {
                    over = true;
                }
            }
            stream.advance();
        }
        if (!succ && tokenOffset < endOffset) {
            tokens.push(new Token(null, endOffset - tokenOffset));
            lastOffset = endOffset;
        }
        if (tokens.length) {
            this.updateNodes(startOffset, startOffset + lastOffset, this.createNodes1(tokens));
        }
        this.dispatchEvent('Changed', {

        });
    },
    parseTokens: function (text, callback){
        callback(new Token(null, text.length));
    },
    concatTwoTokens: function (tokenA, tokenB){
        if (tokenA.type == tokenB.type) {
            return new Token(tokenA.type, tokenA.length + tokenB.length);
        }
        return null;
    }
};

Utils.extend(TextStyler, Eventable);
Utils.extend(TextStyler, Tokenizer);
